/**
  ******************************************************************************
  * @author     Chris
  * @since      2024/6/24 23:22
  *
  * @file       pd_spi_sim.c
  * @brief      SPI simulate driver.
  *
  * @note       This file contains the simulate driver for the SPI.
  *
  * @warning    None.
  ******************************************************************************
  * Change Logs:
  *   Date          Author       Notes
  *   2024/6/24     Chris        the first version
  *
  ******************************************************************************
  */


#include <stm32f4xx_ll_spi.h>
#include <stm32f4xx_ll_gpio.h>
#include "pd_spi_sim.h"

#include <stddef.h>

static uint8_t SPISimulate_readBit(const GPIO *gpio);

static void SPISimulate_writeBit(const GPIO *gpio, FlagStatus BitVal);


#define SCLK(x)     SPISimulate_writeBit(super->sclk, x)
#define SSEL(x, y)  SPISimulate_writeBit(x, y)
#define MOSI(x)     SPISimulate_writeBit(super->mosi, x)
#define MISO()      SPISimulate_readBit(super->miso)

static void SPISimulate_writeBit(const GPIO *gpio, const FlagStatus BitVal) {
    if (gpio) gpio->GPIOx->BSRR = BitVal ? gpio->Pin : gpio->Pin << 16;
}

static uint8_t SPISimulate_readBit(const GPIO *gpio) {
    if (gpio) return GPIO_get(gpio);

    return 0;
}


static void SPISimulate_init_(const SPI *super) {
    for (int i = 0; i < super->sselSize; ++i) GPIO_init(&super->ssel[i]);
    if (super->sclk) GPIO_init(super->sclk);
    if (super->mosi) GPIO_init(super->mosi);
    if (super->miso) GPIO_init(super->miso);

    // Set all slave select to disable
    for (int i = 0; i < super->sselSize; ++i)
        SSEL(&super->ssel[i], 1);

    SCLK(0);
}

static void SPISimulate_start_(const SPI *super, const GPIO *ssel) {
    SSEL(ssel, 0);
}

static uint8_t SPISimulate_swapByte_(const SPI *super, const uint8_t dat) {
    uint8_t ret = 0x00;

    for (int i = 0; i < 8; i++) {
        MOSI(dat & (0x80 >> i));
        SCLK(1);
        if (MISO() == 1) ret |= (0x80 >> i);
        SCLK(0);
    }

    return ret;
}

static void SPISimulate_stop_(const SPI *super, const GPIO *ssel) {
    SSEL(ssel, 1);
}


static void SPISimulate_preConfigGPIO(GPIO *gpio) {
    if (gpio == NULL) return;

    gpio->Mode  = LL_GPIO_MODE_OUTPUT;
    gpio->Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH;
    gpio->Pull  = LL_GPIO_PULL_UP;
}

void SPISimulate_constructor(SPISimulate *this,
                             GPIO *ssel, const uint8_t sselSize, GPIO *sclk, GPIO *mosi, GPIO *miso) {
    static struct SPIVtbl vtbl = {
        .init      = &SPISimulate_init_,
        .start     = &SPISimulate_start_,
        .swap_byte = &SPISimulate_swapByte_,
        .stop      = &SPISimulate_stop_,
    };

    // SPI GPIO pre-config
    GPIO *gpios[] = {sclk, mosi, miso};
    for (int i = 0; i < 3; ++i) SPISimulate_preConfigGPIO(gpios[i]);
    for (int i = 0; i < sselSize; ++i) SPISimulate_preConfigGPIO(&ssel[i]);

    miso->Mode = LL_GPIO_MODE_INPUT;
    SPI_constructor(&this->spi, ssel, sselSize, sclk, mosi, miso);

    this->spi.vptr = &vtbl;

}
